﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ITAP.ContourBuilder.Components;
using ITAP.ContourBuilder.Operations;

namespace ITAP.ContourBuilder.Manipulation
{
    public enum CommandState
    {
        NotExecute,
        Execute,
        EndExecute,
        Canceled
    }

    delegate void VoidDelegate();

    public delegate void ManipulatorEvent(ICommand sender, CommandState eventState);

    public delegate void ObtainPeripheryStateDelegate(GlobalPeripheryState state);

    public delegate void CommandExecutedDelegate(ICommand command);

    public class ContourManipulator
    {
        public event CommandExecutedDelegate CommandExecuted;
        private ContourControl control;
        // Индекс команды, которая была выполнена последней.
        private int currentHistoryIndex = -1;

        /* Правила ведения истории. 
         * При начале работы текущий индекс равен -1, при окончании выполнения первой команды он равен 0, что равно 
         * количеству элементов в списке истории минус 1 элемент (Count - 1).
         * При окончании выполнения второй команды он равен 1, и т.д. 
         * Т.е. он указывает на команду, которая только что завершила выполнение.
         */

        private List<ICommand> history;

        public IEnumerable<ICommand> History
        {
            get { return history; }
        }

        private List<ICommand> currentExecutingCommands;

        public ContourManipulator(ContourControl control)
        {
            this.history = new List<ICommand>();
            this.control = control;
            this.control.PeripheryStateChanged += new PeripheryEvent(this.SetPeripheryState);
            this.currentExecutingCommands = new List<ICommand>();
        }

        public void Terminate(Type typeOfCommand)
        {
            lock (this.currentExecutingCommands)
            {
                List<ICommand> commandsToRemove = new List<ICommand>();
                foreach (ICommand command in this.currentExecutingCommands)
                {
                    if (command.GetType() == typeOfCommand)
                    {
                        command.StateChanged -= this.CommandStateChanged;

                        lock (this.control.Render.CommandsToDraw)
                        {
                            this.control.Render.CommandsToDraw.Remove(command);
                        }

                        commandsToRemove.Add(command);
                        VoidDelegate dlg = new VoidDelegate(command.Terminate);
                        dlg.Invoke();
                    }
                }
                foreach (ICommand command in commandsToRemove)
                    this.currentExecutingCommands.Remove(command);
            }
        }

        /// <summary>
        /// Выполняет сейчас команду В ТОМ ЖЕ ПОТОКЕ, в котором идёт вызов этого метода.     
        /// </summary>
        /// <param name="command"></param>
        public void Execute(ICommand command)
        {
            lock (this.history)
            {
                if (this.currentHistoryIndex != this.history.Count - 1)
                {
                    for (int i = this.currentHistoryIndex + 1; i < this.history.Count; i++)
                        this.history.Remove(this.history[i]);
                }
                this.currentHistoryIndex = this.history.Count - 1;
            }

            ICommand _command = command.Clone() as ICommand;

            try
            {
                lock (this.currentExecutingCommands)
                {
                    foreach (ICommand executingCommand in this.currentExecutingCommands)
                        if (!executingCommand.AllowMultipleInstances && executingCommand.GetType() == _command.GetType())
                            return;

                    _command.StateChanged += new ManipulatorEvent(this.CommandStateChanged);
                    this.currentExecutingCommands.Add(_command);                    
                }

                lock (this.control.Render.CommandsToDraw)
                {
                    this.control.Render.CommandsToDraw.Add(_command);
                }

                _command.Execute();
                this.control.Invalidate();
            }
            catch
            {
                throw new ManipulatorException("При выполнении команды " + command.Name + " произошла ошибка.");
            }
        }

        public void Execute(IOperation operation)
        {

            CommandOperation command = new CommandOperation(this.control, operation.Clone() as IOperation);

            this.Execute(command);
        }

        private void CommandStateChanged(ICommand sender, CommandState eventState)
        {
            if (eventState == CommandState.EndExecute)
            {
                sender.StateChanged -= this.CommandStateChanged;
                lock (this.control.Render.CommandsToDraw)
                {
                    this.control.Render.CommandsToDraw.Remove(sender);
                }
                lock (this.currentExecutingCommands)
                {
                    this.currentExecutingCommands.Remove(sender);
                }

                this.AddToHistory(sender);

                if (this.CommandExecuted != null)
                    this.CommandExecuted.Invoke(sender);

            }
            else if (eventState == CommandState.Canceled)
            {
                sender.StateChanged -= this.CommandStateChanged;
                lock (this.control.Render.CommandsToDraw)
                {
                    this.control.Render.CommandsToDraw.Remove(sender);
                }
                lock (this.currentExecutingCommands)
                {
                    this.currentExecutingCommands.Remove(sender);
                }

                if (this.CommandExecuted != null)
                    this.CommandExecuted.Invoke(sender);
            }

            sender = null;
            this.control.Invalidate();
        }

        public void SetPeripheryState(GlobalPeripheryState state)
        {
            List<ICommand> copyCommands = new List<ICommand>(this.currentExecutingCommands);
            lock (this.currentExecutingCommands)
            {
                foreach (ICommand command in copyCommands)
                {
                    if (command.DisableObtainPeripheryState || command.State != CommandState.Execute)
                        continue;
                    command.ObtainPeripheryState(state);
                }
            }
        }

        public void Undo()
        {
            lock (this.history)
            {
                if (this.currentHistoryIndex >= 0)
                {
                    this.history[this.currentHistoryIndex].Undo();
                    this.currentHistoryIndex--;
                }
                Console.WriteLine(this.currentHistoryIndex.ToString());
                this.control.Invalidate();          
            }
        }

        public void Redo()
        {
            lock (this.history)
            {
                if (this.currentHistoryIndex < this.history.Count - 1)
                {
                    // Redo() выполняется для команды, которая следует за текущей (только что выполненной). 
                    // Текущая команда уже хранит в себе состояние для своего Redo(), и ей можно только сделать 
                    // Undo(). (Это примечание, чтобы на будущее не запутаться, почему тут стоит currentHistoryIndex + 1.)
                    this.history[this.currentHistoryIndex + 1].Redo();
                    this.currentHistoryIndex++;
                }
                Console.WriteLine(this.currentHistoryIndex.ToString());
                this.control.Invalidate();
            }
        }

        public void AddToHistory(ICommand command)
        {
            if (!command.CanUndo)
                return;

            ICommand clone = command.Clone() as ICommand;
            lock (this.history)
            {
                if (this.currentHistoryIndex != this.history.Count - 1)
                    this.history.RemoveRange(this.currentHistoryIndex + 1, this.history.Count - this.currentHistoryIndex - 1);

                this.history.Add(clone);
                this.currentHistoryIndex = this.history.Count - 1;
             //   Console.WriteLine(this.currentHistoryIndex.ToString());
            }
        }
    }
}
